package alpha

import (
	"context"
	"fmt"
	"math/rand"
	"sync"
	"time"

	"github.com/gogf/gf/v2/container/garray"
	"github.com/gogf/gf/v2/container/gqueue"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/util/gconv"
	"github.com/panjf2000/ants/v2"
)

type TranslationRequestBucket struct {
	Content    string
	RetryLimit int
	RetryCount int
}

type ApiCallParam struct {
	ctx            context.Context
	bucket         *TranslationRequestBucket
	successfulList *gqueue.Queue // holds the request entity for simplifying testing
	retryList      *garray.Array
	wg             *sync.WaitGroup
}

const RetryLimit = 3

func CallOpenAiApi(input interface{}) {
	param := input.(*ApiCallParam)
	defer func() {
		param.wg.Done()
	}()

	requestBucket := param.bucket

	mimicSeed := rand.Intn(6) // data range is [0, 3)

	if 0 == mimicSeed {
		// mimic rate limit error
		if requestBucket.RetryCount < requestBucket.RetryLimit {
			requestBucket.RetryCount++
			param.retryList.Append(requestBucket)

			//g.Log().Infof(param.ctx, "rate limit")
			time.Sleep(time.Duration(200+rand.Intn(200)) * time.Millisecond) // [200, 400) ms
		} else {
			//g.Log().Warningf(param.ctx, "retry limitation reached")

			requestBucket.Content = "failed after retrying " + gconv.String(RetryLimit) + " times  " + requestBucket.Content
			param.successfulList.Push(requestBucket)
		}
		return
	}

	if 1 == mimicSeed {
		// mimic bucket truncated error

		param.retryList.Append(&TranslationRequestBucket{
			Content:    fmt.Sprintf("%s-%d-1", requestBucket.Content, time.Now().UnixMilli()),
			RetryLimit: RetryLimit,
			RetryCount: 0,
		})

		param.retryList.Append(&TranslationRequestBucket{
			Content:    fmt.Sprintf("%s-%d-2", requestBucket.Content, time.Now().UnixMilli()),
			RetryLimit: RetryLimit,
			RetryCount: 0,
		})

		return
	}

	if 2 == mimicSeed {
		// mimic other error

		if requestBucket.RetryCount < requestBucket.RetryLimit {
			requestBucket.RetryCount++

			param.retryList.Append(requestBucket)

		} else {
			//g.Log().Warningf(param.ctx, "retry limitation reached")

			requestBucket.Content = "failed after retrying " + gconv.String(RetryLimit) + " times  " + requestBucket.Content
			param.successfulList.Push(requestBucket)
		}

		return
	}

	// not mimic the translation missing error

	requestBucket.Content += " OK"
	g.Log().Infof(param.ctx, "%d %s", requestBucket.RetryCount, requestBucket.Content)
	param.successfulList.Push(requestBucket)
}

func makeFakeBuckets(count int) []*TranslationRequestBucket {
	var result []*TranslationRequestBucket
	for i := 0; i < count; i++ {
		result = append(result, &TranslationRequestBucket{
			Content:    fmt.Sprintf("message_%03d", i),
			RetryLimit: RetryLimit,
			RetryCount: 0,
		})
	}
	return result
}

func Entrance(ctx context.Context, count int) *gqueue.Queue {
	requestBuckets := makeFakeBuckets(count)
	responseBucketQueue := gqueue.New()
	go innerProcess(ctx, requestBuckets, responseBucketQueue)
	return responseBucketQueue
}

func innerProcess(ctx context.Context, requestBuckets []*TranslationRequestBucket, responseBucketQueue *gqueue.Queue) {
	poolOpenAiApi, _ := ants.NewPoolWithFunc(10, func(input interface{}) {
		CallOpenAiApi(input)
	})
	defer func() {
		poolOpenAiApi.Release()
		responseBucketQueue.Close()
	}()

	// copy requestBuckets input param to prevent modification
	currentBuckets := make([]*TranslationRequestBucket, len(requestBuckets))
	copy(currentBuckets, requestBuckets)

	for {
		select {
		case <-ctx.Done(): // 检测context超时、死锁以及进程结束等事件
			g.Log().Warning(ctx, "*************************")
			g.Log().Warning(ctx, "context deadline exceeded")
			g.Log().Warning(ctx, "*************************")
			return
		default:
			var wg sync.WaitGroup
			retryBucketList := garray.New(true)
			for _, bucket := range currentBuckets {
				wg.Add(1)
				_ = poolOpenAiApi.Invoke(&ApiCallParam{
					ctx:            ctx,
					bucket:         bucket,
					successfulList: responseBucketQueue,
					retryList:      retryBucketList,
					wg:             &wg,
				})
			}
			wg.Wait()

			retryCount := retryBucketList.Len()
			if retryCount == 0 {
				// exit loop
				g.Log().Info(ctx, "----------------------")
				g.Log().Info(ctx, "all processes finished")
				g.Log().Info(ctx, "----------------------")
				return
			}

			// loop again to process retryBucketList
			g.Log().Infof(ctx, "start another round for %d retry buckets", retryCount)
			currentBuckets = make([]*TranslationRequestBucket, retryCount)
			retryBucketList.Iterator(func(k int, v interface{}) bool {
				currentBuckets[k] = v.(*TranslationRequestBucket)
				return true
			})
		}
	}
}
